<?php
//incluimos la clase de conexión a la BBDD
include_once ("dblib.php");

/*
 * Esta clase va a implementar todos los métodos necesarios para poder
 * manejar las estructuras jerárquicas tanto de usuarios como de cursos
 * a través de un modelo anidado.
 */

/**
 * This class implements a Nested Set Model.
 * @author CEPAL.
 * @package structHandler
 */

class StructHandler {

    private $tipos;
    //private $caracteresNombre;
    private $separador;
    private $separadorIds;
    private $nombreRaiz;
    private $tableName;
    private $tiposElementos;
    private $erNombres;
    private $nodoInterno;
    private $nodoElemento;


    /**
     * Parameters constructor
     * @param string $nombreTabla
     * @param associative array of string $parametros:
     * ['rel_char']
     * ['id_char']
     * ['tree_char']
     * ['root_name']
     * @param string $tiposNodos. Node types separated by ','.
     */
    function __construct($nombreTabla,$parametros,$debug=false){
        $this->tipos=explode(',',$parametros['value_types']);
        $this->tableName=$nombreTabla;
        //$this->caracteresNombre=array($parametros['rel_char'],$parametros['id_char'],
        //$parametros['tree_char'],$parametros['root_name']);
        $this->separador=$parametros['separator'];
        $this->separadorIds=$parametros['id_char'];
        $this->nombreRaiz=$parametros['root_name'];
        $this->erNombres=$parametros['nodes_name_regex'];
        $this->nodoInterno=$parametros['internal_nodes_type_name'];
        $this->nodoElemento=$parametros['element_nodes_type_name'];

        //Carga el array $tiposElementos buscando nodos de tipo $tipoNodoElemento o $tipoNodoElemento#n
        $this->loadElementsTypes();
        if($debug){
            $men="Tipos Nodos:";
            foreach ($this->tipos as $t) $men=$men." $t, ";

            $men1="Tipos Elementos: ";
            if ($this->tiposElementos!=NULL)foreach ($this->tiposElementos as $t) $men1=$men1.$t['value']."=>".$t['type']." ";
            
            $mensaje="TableName:$nombreTabla. RelationSep: $this->separador.
            IdsSep: $this->separadorIds. TreeSep: $this->separador. RootName: $this->nombreRaiz.
            $men $men1. InternalNodes: $this->nodoInterno .ElementNodes: $this->nodoElemento." ;
            //DEBUG MESSAGES
            trigger_error($mensaje,E_USER_NOTICE);

        }
    }


    /**
     * This function loads in $this->tiposElementos a list of the nodes with type LIKE 'element%'
     */
    private function loadElementsTypes(){
        $query="SELECT id,value,type FROM ".$this->tableName." WHERE type LIKE '".$this->nodoElemento."%'";
        $result=DB::query($query);
        if(DB::num_rows($result)!=0){
            $this->tiposElementos=array();
            $cont=0;
            while($row=DB::fetch_assoc($result)) {
                $this->tiposElementos[$cont]=$row;
                $pos=strrpos($this->tiposElementos[$cont]['value'],$this->separador);
                $this->tiposElementos[$cont]['value']=substr($this->tiposElementos[$cont]['value'],$pos+1);
                $cont++;
            }
        }
        else $this->tiposElementos=NULL;
    }


    /**
     * It determines if a type name is valid.
     * @param string $type contains a type name to validate
     * @return boolean true if the type is valid.
      */
    private function validateType($type){
        if($type==$this->nodoInterno) return true;
        else if(preg_match('/'.$this->nodoElemento.'(#[0-9]*)?$/',$type)>0){
            return true;
        }
        else if(in_array($type,$this->tipos)!=false) return true;
            //if(array_search($type,$this->tipos)!=false) return true;
            else return false;

    }


    /**
     * It determines if a node name is valid (not contains any special characters).
     * @param string $name contains the node name to validate.
     * @return boolean true if the name is valid.
     */
    private function validateName($name){
        if (preg_match($this->erNombres, $name)!=0) return true;
        else return false;
        /*
        foreach ($this->caracteresNombre as $c){
            if(strpos($name,$c)) return false;
        }
        return true;
         *
         */

    }


    /**
     * It determines if the structure table is empty.
     * @return boolean true if the table is empty.
     */
    private function  emptyTable() {
        $nombreTabla=$this->tableName;

        //comprobamos cuantas filas hay
        $result = DB::query("SELECT 1 from ".$nombreTabla." LIMIT 1;");
        if(DB::num_rows($result)==0) return true;
        else return false;

    }


    /**
     * Function that creates the complete path of the node knowing the parent path.
     * NOTE: the name of the children node shouldn't have slashes inside the path.
     * This path should be conform with this structure: /DIR1/DIR2/.../DIRN (no final slash added)
     * @param string $nodeName refers the given node to insert
     * @param string $parentName refers the parent node of the given node
     * @return string which contains the path.
     */
    private function createName($nodeName, $parentName){
	//si no es una raíz, concatenamos antes el nombre del padre
	if (!$parentName == "") return $parentName .$this->separador. $nodeName;
	//si el nombre es una raíz, simplemente concatenamos al comienzo la barra inicial
	else return $this->separador.$nodeName;

    }


    /**
     * Function that returns the node name without the complete path.
     * @param string $name contains the node name.
     * @return string which contains the local name.
     */
    private function getRealName($name){
	$fields= explode ($this->separador,$name);
        $nc=count($fields);
	return  $fields[$nc-1];
    }


    /**
     * Function that returns the node's path without the node name.
     * @param string $nodeName
     * @return string which contains the path
     */
    private function getPath($nodeName){
	$ultimabarra = strrpos($nodeName,$this->separador);
	return substr($nodeName,0,$ultimabarra);
    }


    /**
     * Function that renames a subtree (for move subtrees)
     * @param string $node contains the root's name of the subtree.
     * @param string $newParent with a valid node name.
     */
    private function renameSubtree($node,$newParent) {
	//hay que reemplazar la cadena del nodo por la del nuevo padre.
	$nuevaCad = $newParent;
	$viejaCad = $this->getPath($node);
	$arrayNodos= array();
	$arrayNodos = $this->getAllNodes($node);
	foreach ($arrayNodos as $value){
            $nombreNuevo = str_ireplace($viejaCad,$nuevaCad,$value);
            $this->renameNode($value,$nombreNuevo);
	}
    }


    /**
     * Function that renames a node
     * @param string $oldName contains the name of a existing node.
     * @param string $newName contains a valid node name.
     * @return integer 1 if the name was changed, -1 if the name can't be changed.
     */
    private function renameNode($oldName,$newName) {
        $nombreTabla=$this->tableName;
        //Ya existe nodo con nombre nuevo?
        if($this->existsNode($nombrenuevo)) return 1;
        else {
            $query="UPDATE ".$nombreTabla." SET value='".$newName."' WHERE value='".$oldName."'";
            $res=DB::query($query);
            if($res==FALSE) return -1;
            else return 1;
        }
    }


    /**
     * Function that inserts a child into a tree.
     * If the $parentName is "" the new node will be the right brother of $brotherName.
     * If the $brotherName is "" the new node will be the leftest child.
     * ($parentName=="" XOR $brotherName=="")
     * @param string $nodeName contains the name of the new child
     * @param string $type contains a node type name.
     * @param string $parentName contains the name of the parent.
     * @param string $brotherName contains the name of the left brother.
     * @return boolean true if the child was inserted.
     */
    public function addChild($nodeName, $type, $parentName, $brotherName,$debug=false) {
        $nombreTabla=$this->tableName;
        //Comprobar que el tipo es correcto:
        if (!$this->validateType($type)) {
            if($debug){trigger_error("Invalid type: $type. Function will return false.",E_USER_NOTICE);}
            return false;
        }
        //Comprobar que el nombre es correcto:
        if(!$this->validateName($nodeName)){
            if($debug){trigger_error("Invalid name: $nodeName. Function will return false.",E_USER_NOTICE);}
            return false;
        }
        //nombreNodo viene como nombre simple, hay que convertirlo en su nombre real con ruta

	//Si queremos añadir un hermano derecho a un hermano izquierdo ya conocido.
        if ((($parentName)=="")&&(($brotherName)!="")) {
            //Comprobar que exista el nodo $nombreHermano
            if(!$this->existsNode($brotherName)){
                if($debug){trigger_error("Node can not be inserted. Node: $brotherName DOESN'T EXIST. Function will return false.",E_USER_NOTICE);}
                return false;
            }
            $nodoPadre = $this->getParent($brotherName);
            //Comprobar que no exista ya un nodo con el nombre del que queremos insertar
            if ($this->existsBrother($nodoPadre,$nodeName)) {
                if($debug){trigger_error("Node can not be inserted. Another node exists named $nodeName. Function will return false.",E_USER_NOTICE);}
                return false;
            }
            $nombrePadreReal = $this->getPath($brotherName);
            $nombreRealNodo = $this->createName($nodeName,$nombrePadreReal);
            $this->createBrother($brotherName,$nombreRealNodo,$type);
            if($debug){trigger_error("Node $nombreRealNodo has been inserted. Function will return true.",E_USER_NOTICE);}
            $this->loadElementsTypes();
            return true;
        }

        //Si queremos añadir un hijo a un padre sin hijos o aunque tenga hijos si no nos importa el orden
        else if ((($parentName)!="")&&(($brotherName)=="")) {
            //Comprobar que exista el nodo $nombrePadre
            if(!$this->existsNode($parentName)){
                if($debug){trigger_error("Node can not be inserted. Node: $nodeName. Parent: $parentName DOESN'T EXIST. Function will return false.",E_USER_NOTICE);}
                return false;
            }
            //Comprobar que no exista ya un nodo con el nombre del que queremos insertar
            if ($this->existsBrother($parentName,$nodeName)) {
                if($debug){trigger_error("Node can not be inserted. Another node exists named $nodeName. Function will return false.",E_USER_NOTICE);}
                return false;
            }
            $nombreRealNodo = $this->createName($nodeName,$parentName);
            $this->createChild($parentName,$nombreRealNodo,$type);
            if($debug){trigger_error("Node $nombreRealNodo has been inserted. Function will return true.",E_USER_NOTICE);}
            $this->loadElementsTypes();
            return true;
	}

        //Si los dos estan vacíos se añade una raíz
        else if ((($parentName)=="")&&(($brotherName)=="")) {
            //Comprobar que no exista ya una raíz
            if(!$this->emptyTable()) {
                if($debug){trigger_error("Root can not be inserted. A root exists now. Function will return false.",E_USER_NOTICE);}
                return false;
            }
            $nombreRealNodo = $this->createName($nodeName,$parentName);
            if($debug){trigger_error("Root $nombreRealNodo has been inserted. Function will return true.",E_USER_NOTICE);}
            return $this->createRoot($nombreRealNodo,$type);
	}
        //Si los dos argumentos contienen algo devolver -1
        else{
            if($debug){trigger_error("Invalid use of arguments.",E_USER_NOTICE);}
            return false;
        }
    }


    /**
     * Function that inserts a child as the leftest child of the parent node.
     * @param string $parentName contains the correct name of the parent.
     * @param string $nodeName contains the correct name of the child.
     * @param string $type contains the type name of the node.
     */
    private function createChild($parentName,$nodeName, $type) {
        $nombreTabla=$this->tableName;
        $query=array("LOCK TABLE ".$nombreTabla." WRITE;",
                        "SELECT @myLeft := lft FROM ".$nombreTabla. "
                            WHERE value = '" .$parentName. "';",
                        "UPDATE ".$nombreTabla." SET rgt = rgt + 2
                            WHERE rgt > @myLeft;",
                        "UPDATE ".$nombreTabla." SET lft = lft + 2
                            WHERE lft > @myLeft;",
                        " INSERT INTO ".$nombreTabla."(value,type, lft, rgt)
                            VALUES('".$nodeName."','".$type."', @myLeft + 1, @myLeft + 2);",
                        "UNLOCK TABLES;");
        foreach ($query as $q){
            DB::query($q);
        }
    }


    /**
     * Function that creates a right brother.
     * @param string $leftBrotherName contains an existing node name.
     * @param string $nodeName contains the new node name.
     * @param string $type contains a valid type node name.
     */
    private function createBrother($leftBrotherName, $nodeName, $type) {
        $nombreTabla=$this->tableName;
        $query=array("LOCK TABLE ".$nombreTabla." WRITE;",
                        "SELECT @myRight := rgt FROM ".$nombreTabla."
                            WHERE value = '" .$leftBrotherName. "';",
                        "UPDATE ".$nombreTabla." SET rgt = rgt + 2
                            WHERE rgt > @myRight;",
                        "UPDATE ".$nombreTabla." SET lft = lft + 2
                            WHERE lft > @myRight;",
                        "INSERT INTO ".$nombreTabla."(value,type, lft, rgt)
                            VALUES('".$nodeName."', '".$type."', @myRight + 1, @myRight + 2);",
                        "UNLOCK TABLES;");
        foreach ($query as $q){
            DB::query($q);
        }
    }


    /**
     * Function that creates the root of the tree.
     * @param string $rootName contains a valid node name.
     * @param string $type contains a valid type name.
     */
    private function createRoot($rootName,$type) {
        $nombreTabla=$this->tableName;
        //insertar nodo en la estructura
        DB::query("INSERT INTO ".$nombreTabla."(value,type,lft,rgt) values ('".$rootName."','".$type."',1,2);");
    }


    /**
     * Function that moves a complete subtree as a child of $newParent node or as the right
     * brother of $newLeftBrother
     * @param string $nodeName contains the name of the node to move.
     * @param string $newParent contains an existing node name or "".
     * @param string $newLeftBrother contains an existing node name or "".
     * @return integer 1 if the subtree has been moved, -1 if it couldn't move it.
     */
    private function moveSubtree($nodeName,$newParent,$newLeftBrother) {
        $nombreTabla=$this->tableName;
        //Comprobar que existe el nodo
        if(!$this->existsNode($nodeName)) return -1;
        //recuperamos el nodo origen
        $query="SELECT lft,rgt FROM ".$nombreTabla." WHERE value='".$nodeName."'";
        $result=DB::query($query);
        $row=DB::fetch_assoc($result);
        $origen=$row;
        //Si se ha indicado el nombre del nuevo padre, se coloca a como el hijo
        //mas a la izquierda
        if((isset($newParent))||($newParent!="")){
            //Comprobar que exista el padre
            if(!$this->existsNode($newParent)) return -1;
            //si ya existe un hermano con ese nombre no se hace nada
            if ($this->existsBrother($newParent,$nodeName)) return -1;
            //Comprobar que no se pretenda mover un nodo como descendiente de si mismo.
            if ($this->isParent($nodeName,$newParent)) return -1;
            //Recuperar el nodo padre
            $query="SELECT lft,rgt FROM ".$nombreTabla." WHERE value='".$newParent."'";
            $result=DB::query($query);
            $row=DB::fetch_assoc($result);
            $nodoR=$row;
            $referencia=$row['lft'];
            $padre=$newParent;
        }
        else //Se indica el nuevo hermano izquierdo del nodo a mover
        if ((isset($newLeftBrother))||($newLeftBrother!="")){
            //Comprobar que exista el hermano
            if(!$this->existsNode($newLeftBrother)) return -1;
            //si ya existe un hermano con ese nombre no se hace nada
            if ($this->existsBrother($this->getParent($newLeftBrother),$nodeName)) return -1;
            //Comprobar que no se pretenda mover un nodo como descendiente de si mismo.
            if ($this->isParent($nodeName,$newLeftBrother)) return -1;
            //Recuperar el nodo hermano
            $query="SELECT lft,rgt FROM ".$nombreTabla." WHERE value='".$newLeftBrother."'";
            $result=DB::query($query);
            $row=DB::fetch_assoc($result);
            $nodoR=$row;
            $referencia=$row['rgt'];
            $padre=$this->getParent($newLeftBrother);
        }
        else{
            //Errores en el paso de parámetros
            if(isset($newLeftBrother) && isset($newParent)) return -1;
            if(($newLeftBrother=="")&&($newParent=="")) return -1;
            if(!isset($newLeftBrother) && !isset($newParent)) return -1;
        }

        //comenzamos a configurar parámetros de las consultas:
        $li1=$origen['lft']-1;
        $ld1=$origen['rgt']+1;

        //Si se está moviendo de izquierda a derecha
        if($origen['lft']<$nodoR['lft']){
            $li2=$origen['rgt'];
            $ld2=$referencia+1;
            $ancho=-($origen['rgt']-$origen['lft']+1);
            $distancia=$origen['rgt']-$referencia;
        }
        //Si es de derecha a izquierda
        else{
            $li2=$referencia;
            $ld2=$origen['rgt']+1;
            $ancho=$origen['rgt']-$origen['lft']+1;
            $distancia=$origen['lft']-$referencia-1;
        }
        //Comienzan las consultas
                        //1.-Mover subárbol a su sitio
        $query=array(   "LOCK TABLE ".$nombreTabla." WRITE;",
                        "UPDATE ".$nombreTabla." SET lft =".$distancia."-lft WHERE lft>".$li1." AND lft <".$ld1,
                        "UPDATE ".$nombreTabla." SET rgt =".$distancia."-rgt WHERE rgt>".$li1." AND rgt <".$ld1,
                        //2.-Hacer hueco.
                        "UPDATE ".$nombreTabla." SET lft =".$ancho."+lft WHERE lft>".$li2." AND lft <".$ld2,
                        "UPDATE ".$nombreTabla." SET rgt =".$ancho."+rgt WHERE rgt>".$li2." AND rgt <".$ld2,
                        //3.-Restaurar lft y rgt del sub rbol movido
                        "UPDATE ".$nombreTabla." SET lft =-lft WHERE lft<0",
                        "UPDATE ".$nombreTabla." SET rgt =-rgt WHERE rgt<0"
                    );
        foreach ($query as $q){
            DB::query($q);
        }
        DB::query("UNLOCK TABLES;");
        //4.-Renombrar el sub rbol
        $this->renameSubtree($nodeName,$padre);
        $this->loadElementsTypes();
        return 1;
    }


    /**
     * Function that deletes a subtree.
     * @param string $name contains the node name of the root.
     * @return boolean false if the node doesn't exist.
     */
    private function deleteTree($name) {
        $nombreTabla=$this->tableName;
        if(!($this->existsNode($name))) return false;

        $query=array(   "LOCK TABLE ".$nombreTabla." WRITE",
                        "SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1
                            FROM ".$nombreTabla." WHERE value='".$name."'",
                        "DELETE FROM ".$nombreTabla." WHERE lft BETWEEN @myLeft AND @myRight;",
                        "UPDATE ".$nombreTabla." SET rgt = rgt - @myWidth WHERE rgt > @myRight;",
                        "UPDATE ".$nombreTabla." SET lft = lft - @myWidth WHERE lft > @myRight;",
                        "UNLOCK TABLES;");
        foreach ($query as $q){
            DB::query($q);
        }
        $this->loadElementsTypes();
        return true;
    }


    /**
     * Function that determines if two nodes are in the same branch.
     * @param string $node1 contains an existing node name.
     * @param string $node2 contains an existing node name.
     * @return boolean true if the two nodes are in the same branch.
     */
    private function isParent($node1,$node2) {
        $der_nodo1 = $this->getRight($node1);
        $izq_nodo1 = $this->getLeft($node1);
        $der_nodo2 = $this->getRight($node2);
        $izq_nodo2=  $this->getLeft($node2);
        if (($der_nodo1 > $der_nodo2) && ($izq_nodo1 < $izq_nodo2)) return true;
        else return false;
    }


    /**
     * Function that determines if exists a concrete child of a node.
     * @param string $parentName contains the name of the parent.
     * @param string $name contains the child name.
     * @return boolean true if the node exists.
     */
    private function existsBrother($parentName, $name){
	$nombre = $this->getRealName($name);
	$arrayNodos = $this->getInmChildren("value",$parentName);
	foreach ($arrayNodos as $value){
            $nombreReal=$this->getRealName($value['value']);
            if ($nombreReal == $nombre) return true;
	}
	return false;
    }


    /**
     * Function that determines if a node exists.
     * @param string $nodeName contains a node name.
     * @return boolean true if the node exists.
     */
    public function existsNode($nodeName) {
        $nombreTabla=$this->tableName;
        $result = DB::query("SELECT count(*) from " .$nombreTabla. " where value='".$nodeName."'");
        $row = DB::fetch_array($result);
        if ($row['count(*)'] == 0) {return false;}
        else return true;
    }


    /**
     * Function that determines if a node is a leaf or not.
     * @param string $nodeName contains a node name.
     * @return boolean true if the node is a leaf
     */
    private function isLeaf($nodeName) {
        $nombreTabla=$this->tableName;
        $query="SELECT lft,rgt from ".$nombreTabla." where value='".$nodeName."';";
        $result=DB::query($query);
        //comprobar si existe el nodo $nodeName
        if(DB::num_rows($result)==0)return false;
        $row=DB::fetch_assoc($result);
        if (($row['lft'] +1) == ($row['rgt']))	return true;
        else return false;
    }


    /**
     * Function that determines if a node is the root or not.
     * @param string $nodeName contains a node name.
     * @return boolean true if the node is the root.
     */
    private function isRoot($nodeName) {
        $nombreTabla=$this->tableName;
        $query = "SELECT lft from ".$nombreTabla." where value='".$nodeName."';";
        $result=DB::query($query);
        //Comprobar si existe el nodo $nombreNodo
        if(DB::num_rows($result)==0)return false;
        $row = DB::fetch_array($result);
        if (($row['lft']) == 1)	return true;
        else return false;
    }


    /**
     * Function that return the parent name of a node.
     * @param string $node contains an existing node name
     * @return string with the name of the parent or -1 if the node doesn't exist.
     */
    private function getParent($node){
        $nombreTabla=$this->tableName;
	$query = "SELECT value FROM ".$nombreTabla."
            WHERE lft=(select MAX(lft)
            from ".$nombreTabla."
            where lft<".$this->getLeft($node)."
            and rgt>".$this->getRight($node).");";
        $result =   DB::query($query);
        //Comprobar si existe el nodo $nodo
        if(DB::num_rows($result)==0) return -1;
        $row = DB::fetch_array($result);
        return ($row['value']);
    }


    /**
     * Function that returns the left value of a node
     * @param string $nodeName contains an existing node name.
     * @return integer lft value of the node or -1 if the node doesn't exist.
     */
    private function getLeft($nodeName) {
        $nombreTabla=$this->tableName;
        $queryIZQ = "SELECT lft from ".$nombreTabla." where value='".$nodeName."';";
        $resultIZ =   DB::query($queryIZQ);
        //Comprobar si existe el nodo $nombreNodo
        if(DB::num_rows($resultIZ)==0) return -1;
        $rowIZ = DB::fetch_array($resultIZ);
        return ($rowIZ['lft']);
    }


    /**
     * Function that returns the right value of a node
     * @param string $nodeName contains an existing node name.
     * @return integer rgt value of the node or -1 if the node doesn't exist.
     */
    private function getRight($nodeName) {
        $nombreTabla=$this->tableName;
        $queryDER = "SELECT rgt from ".$nombreTabla." where value='".$nodeName."';";
        $resultDER =   DB::query($queryDER);
        //Comprobar si existe el nodo $nombreNodo
        if(DB::num_rows($resultDER)==0) return -1;
        $rowDER = DB::fetch_array($resultDER);
        return ($rowDER['rgt']);
    }


    /**
     * Function that returns the id value of a node
     * @param string $nodeName contains an existing node name.
     * @return integer id value of the node or -1 if the node doesn't exist.
     */
    public function getId($nodeName) {
         $nombreTabla=$this->tableName;
        $result = DB::query("SELECT id from " .$nombreTabla. " where value='".$nodeName."'");

        if (DB::num_rows($result)!=0){
            $row = DB::fetch_array($result);
            return $row['id'];
        }else return -1;
    }


    /**
     * Function that returns the inmediates children of a node.
     * @param string $campos contains the fields we want to get separated with ','
     * @param string $root contains the root name.
     * @return associative array with the fields as tags.
     */
    private function getInmChildren($campos,$root) {
        $nombreTabla=$this->tableName;
        $fields= explode (',',$campos);
        $nc=count($fields);
        $listacampos=implode(", node.",$fields);
        $listacampos="node.".$listacampos;
        $query="SELECT ".$listacampos.", (COUNT(parent.value) - (sub_tree.depth + 1)) AS depth
                FROM "  .$nombreTabla." AS node,"
                        .$nombreTabla." AS parent,"
                        .$nombreTabla." AS sub_parent,
                        (SELECT node.value, (COUNT(parent.value) - 1) AS depth
                            FROM "  .$nombreTabla." AS node,"
                                    .$nombreTabla." AS parent
                            WHERE node.lft BETWEEN parent.lft AND parent.rgt
                            AND node.value ='".$root."'
                            GROUP BY node.value
                            ORDER BY node.lft
                            )AS sub_tree
                WHERE node.lft BETWEEN parent.lft AND parent.rgt
                    AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
                    AND sub_parent.value = sub_tree.value
		GROUP BY node.value
		HAVING depth = 1
		ORDER BY node.lft;";
        $result=DB::query($query);
        $salida=array();
        $j=0;
        while($row=DB::fetch_assoc($result)) {
           $salida[$j]=$row;
            $j++;
        }
        DB::free_result($result);

        return $salida;
    }


    /**
     * Function that returns the names of all nodes of the subtree with node as root.
     * @param string $rootNode contains the name of the root of subtree.
     * @return array with the names, -1 if the rootNode doesn't exist.
     */
    private function getAllNodes($rootNode) {
	$nombreTabla=$this->tableName;
        $query = "SELECT node.value FROM ".$nombreTabla." AS node, ".$nombreTabla." AS parent
                    WHERE node.lft BETWEEN parent.lft AND parent.rgt
                    AND parent.value = '".$rootNode."' ORDER BY node.lft;";
        $result=DB::query($query);
        $salida=array();
        $j=0;
        while($row=DB::fetch_assoc($result)) {
            $salida[$j]=$row['value'];
            $j++;
        }
        if ($j==0) return -1;
        DB::free_result($result);
        return $salida;

    }


    /**
     * Function that returns all leafs of a subtree with $node as root.
     * @param string $nodo contains a node name.
     * @return array with the leafs names or -1 if the node doesn't exists.
     */
    private function getLeafsSubTree($nodo) {
	$nombreTabla=$this->tableName;
        $der = $this->getRight($nodo);
        $izq = $this->getLeft($nodo);
        $query = "SELECT value FROM ".$nombreTabla." WHERE rgt = lft + 1 AND rgt < ".$der." AND lft > ".$izq.";";
        $result=DB::query($query);
        $salida=array();
        $j=0;
        while($row=DB::fetch_assoc($result)) {
            $salida[$j]=$row['value'];
            $j++;
        }
        if ($j==0) return -1;
        DB::free_result($result);
        return $salida;

    }


    /**
     * Function that returns the type of a node.
     * @param string $nodo contains the node name.
     * @return string with the type of the node or -1 if the node doesn't exist.
     */
    public function getType($nodo){
        $nombreTabla=$this->tableName;
        $query="SELECT type FROM ".$nombreTabla." WHERE value='".$nodo."'";
        $result=DB::query($query);
        if(!($row=DB::fetch_assoc($result))) return -1;
        else return $row['type'];

    }


    /**
     * Function that returns the type of a node.
     * @param integer $id contains the node id.
     * @return string with the type of the node or -1 if the node doesn't exist.
     */
    private function getTypeById($id){
        $nombreTabla=$this->tableName;
        $query="SELECT type FROM ".$nombreTabla." WHERE id=".$id;
        $result=DB::query($query);
        if(!($row=DB::fetch_assoc($result))) return -1;
        else return $row['id'];

    }


    /**
     * Function that returns the root of the tree.
     * @return string root name.
     */
    private function getRoot(){
        $nombreTabla=$this->tableName;
        $query="SELECT value FROM ".$nombreTabla." WHERE lft=1";
        $result=DB::query($query);
        $row=DB::fetch_assoc($result);
        return $row['value'];

    }

    public function getElements(){
        $this->loadElementsTypes();
        return $this->tiposElementos;
    }

    public function isElementName($name){
        $this->loadElementsTypes();
        foreach ($this->tiposElementos as $t){
            if($name==$t['value']) return true;
        }
        return false;
        
    }

}
?>
